package com.pactera.redis.utils;

import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.*;
import org.springframework.util.ObjectUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Redis工具类
 **/
public class RedisUtils {

    private static final Logger log = LoggerFactory.getLogger(RedisUtils.class);

    private RedisUtils() {
    }

    private static RedisTemplate redisTemplate;
    private static String prefix;
    private static Long ttl;
    private static boolean ignoreCase = true;
    private static boolean usePrefix = false;

    public static RedisUtils builder(RedisTemplate redisTemplate, String prefix, Long ttl, boolean ignoreCase) {
        RedisUtils.redisTemplate = redisTemplate;
        RedisUtils.usePrefix = StringUtils.isBlank(prefix) ? false : true;
        RedisUtils.prefix = StringUtils.isBlank(prefix) ? "" : prefix;
        RedisUtils.ttl = ObjectUtils.isEmpty(ttl) ? -1L : ttl;
        RedisUtils.ignoreCase = ignoreCase;
        return new RedisUtils();
    }

    public static RedisTemplate redisTemplate() {
        return redisTemplate;
    }

    public static boolean isIgnoreCase() {
        return ignoreCase;
    }

    public static boolean isUsePrefix() {
        return usePrefix;
    }

    public static String getPrefix() {
        return prefix;
    }

    public static Long getTtl() {
        return ttl;
    }

    /**
     * 生成缓存key
     *
     * @param group 分组
     * @param key   key
     * @author: liqingdian
     */
    public static String redisKey(Object group, Object... key) {
        Assert.isNotNull(group, "[group]不能为空");
        String redisKey;
        if (ArrayUtils.isEmpty(key)) {
            redisKey = group.toString();
        } else {
            redisKey = Joiner.on("-").useForNull("UNKNOWN").join(group, "::", key);
        }
        return RedisUtils.ignoreCase ? redisKey.toUpperCase() : redisKey;
    }

    /**
     * 拼接缓存前缀-用于需要和其它项目公用缓存的场景
     *
     * @param key 缓存key
     * @author: liqingdian
     */
    public static String keyPrefix(String key) {
        Assert.isNotBlank(key, "[key]不能为空");
        String redisKey;
        if (usePrefix) {
            redisKey = Joiner.on("-").join(prefix, key);
        } else {
            redisKey = key;
        }
        return RedisUtils.ignoreCase ? redisKey.toUpperCase() : redisKey;
    }

    /**
     * 刷新失效时间
     *
     * @param key 缓存key
     * @author: liqingdian
     */
    public static boolean expire(String key) {
        try {
            return redisTemplate.expire(keyPrefix(key), ttl, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 设置失效时间
     *
     * @param key     缓存key
     * @param seconds 秒数
     * @author: liqingdian
     */
    public static boolean expire(String key, long seconds) {
        try {
            return redisTemplate.expire(keyPrefix(key), seconds, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 获取失效时间
     *
     * @param key 缓存key
     * @author: liqingdian
     */
    public static long getExpire(String key) {
        return redisTemplate.getExpire(keyPrefix(key), TimeUnit.SECONDS);
    }

    /**
     * 是否存在key
     *
     * @param key 缓存key
     * @author: liqingdian
     */
    public static boolean hasKey(String key) {
        return redisTemplate.hasKey(keyPrefix(key));
    }

    /**
     * 获取所有缓存key
     *
     * @param pattern 匹配模板
     * @author: liqingdian
     */
    public static Set<String> keys(String pattern) {
        Set<String> keys = redisTemplate.keys(keyPrefix(pattern));
        return keys.stream().map(a -> {
            if (usePrefix) {
                return a.replaceFirst(prefix, "");
            }
            return a;
        }).collect(Collectors.toSet());
    }

    /**
     * 删除缓存
     *
     * @param keys 缓存key，可以是一个也可以是多个
     * @author: liqingdian
     */
    public static void delete(String... keys) {
        Assert.isNotEmpty(keys, "[keys]不能为空");
        if (keys.length == 1) {
            redisTemplate.delete(keyPrefix(keys[0]));
        } else {
            Set<String> keySet = Lists.newArrayList(keys).stream().filter(StringUtils::isNotBlank).map(a -> keyPrefix(a)).collect(Collectors.toSet());
            redisTemplate.delete(keySet);
        }
    }

    /**
     * 获取ValueOperations
     *
     * @author: liqingdian
     */
    private static <T> ValueOperations<String, T> getValue() {
        return redisTemplate.opsForValue();
    }

    /**
     * 获取缓存
     *
     * @param key   缓存key
     * @param clazz 类型
     * @author: liqingdian
     */
    public static <T> T get(String key, Class<T> clazz) {
        try {
            return (T) getValue().get(keyPrefix(key));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 添加缓存
     *
     * @param key     缓存key
     * @param value   缓存值对象
     * @param seconds 失效时间（秒）
     * @author: liqingdian
     */
    public static boolean put(String key, Object value, long seconds) {
        try {
            getValue().set(keyPrefix(key), value, seconds, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
        return true;
    }

    /**
     * 添加缓存
     *
     * @param key   缓存key
     * @param value 缓存值对象
     * @author: liqingdian
     */
    public static boolean put(String key, Object value) {
        return put(key, value, ttl);
    }

    /**
     * 递增/递减
     *
     * @param key   缓存key
     * @param delta 递增因子 正数-递增，负数-递减
     * @author: liqingdian
     */
    public static long increment(String key, long delta) {
        Assert.isTrue(delta != 0, "递增因子不能等于0");
        return getValue().increment(keyPrefix(key), delta);
    }

    /**
     * 获取HashOperations
     *
     * @author: liqingdian
     */
    private static <T> HashOperations<String, String, T> getHash() {
        return redisTemplate.opsForHash();
    }

    /**
     * hashGet
     *
     * @param key   缓存key
     * @param item  map的key
     * @param clazz 类型
     * @author: liqingdian
     */
    public static <T> T hashGet(String key, String item, Class<T> clazz) {
        return (T) getHash().get(keyPrefix(key), item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 缓存key
     * @author: liqingdian
     */
    public static Map<String, Object> hashGet(String key) {
        return getHash().entries(keyPrefix(key));
    }

    /**
     * hashSet
     *
     * @param key     缓存key
     * @param item    Map的key
     * @param data    缓存值对象
     * @param seconds 失效时间（秒）
     * @author: liqingdian
     */
    public static boolean hashPut(String key, String item, Object data, long seconds) {
        Assert.isNotBlank(item, "[item]不能为空");
        Assert.isNotNull(data, "[data]不能为空");
        try {
            getHash().put(keyPrefix(key), item, data);
            expire(key, seconds);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
        return true;
    }

    /**
     * hashSet
     *
     * @param key  缓存key
     * @param item Map的key
     * @param data 缓存值对象
     * @author: liqingdian
     */
    public static boolean hashPut(String key, String item, Object data) {
        return hashPut(key, item, data, ttl);
    }

    /**
     * hashSet
     *
     * @param key     缓存key
     * @param map     Map集合
     * @param seconds 失效时间（秒）
     * @author: liqingdian
     */
    public static boolean hashPut(String key, Map<String, Object> map, long seconds) {
        Assert.isNotEmpty(map, "[map]不能为空");
        try {
            getHash().putAll(keyPrefix(key), map);
            expire(key, seconds);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
        return true;
    }

    /**
     * hashSet
     *
     * @param key 缓存key
     * @param map Map集合
     * @author: liqingdian
     */
    public static boolean hashPut(String key, Map<String, Object> map) {
        return hashPut(key, map, ttl);
    }

    /**
     * 删除hash表中的值
     *
     * @param key   缓存key
     * @param items 键
     * @author: liqingdian
     */
    public static void hashDelete(String key, Object... items) {
        Assert.isNotEmpty(items, "[items]不能为空");
        getHash().delete(keyPrefix(key), items);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @author: liqingdian
     */
    public static boolean hashHasKey(String key, String item) {
        Assert.isNotBlank(item, "[item]不能为空");
        return getHash().hasKey(keyPrefix(key), item);
    }

    /**
     * hash递增/递减 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @author: liqingdian
     */
    public static double hashIncrement(String key, String item, double by) {
        Assert.isTrue(by != 0, "[by]不能等于0");
        return getHash().increment(keyPrefix(key), item, by);
    }

    /**
     * 获取Set
     *
     * @author: liqingdian
     */
    private static SetOperations<String, Object> getSet() {
        return redisTemplate.opsForSet();
    }

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 缓存key
     * @author: liqingdian
     */
    public static Set<Object> setGet(String key) {
        try {
            return getSet().members(keyPrefix(key));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   缓存key
     * @param value 值
     * @author: liqingdian
     */
    public static boolean setHasKey(String key, Object value) {
        Assert.isNotNull(value, "[value]不能为空");
        try {
            return getSet().isMember(keyPrefix(key), value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 将set数据放入缓存
     *
     * @param key     缓存key
     * @param seconds 失效时间
     * @param values  值
     * @author: liqingdian
     */
    public static long setPut(String key, long seconds, Object... values) {
        Assert.isNotEmpty(values, "[values]不能为空");
        try {
            Long count = getSet().add(keyPrefix(key), values);
            expire(key, seconds);
            return count;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return 0;
        }
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    缓存key
     * @param values 值
     * @author: liqingdian
     */
    public static long setPut(String key, Object... values) {
        return setPut(key, ttl, values);
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 缓存key
     * @author: liqingdian
     */
    public static long setSize(String key) {
        try {
            return getSet().size(keyPrefix(key));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return 0;
        }
    }

    /**
     * 移除值为value的元素
     *
     * @param key    缓存key
     * @param values 移除的值
     * @author: liqingdian
     */
    public static long setRemove(String key, Object... values) {
        Assert.isNotEmpty(values, "[values]不能为空");
        try {
            Long count = getSet().remove(keyPrefix(key), values);
            return count;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return 0;
        }
    }

    /**
     * 获取list
     *
     * @author: liqingdian
     */
    private static ListOperations<String, Object> getList() {
        return redisTemplate.opsForList();
    }

    /**
     * 获取list缓存的内容
     *
     * @param key   缓存key
     * @param start 开始位置
     * @param end   结束位置
     * @author: liqingdian
     */
    public static List<Object> listGet(String key, long start, long end) {
        try {
            return getList().range(keyPrefix(key), start, end);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引
     * @author: liqingdian
     */
    public static Object listGet(String key, long index) {
        try {
            return getList().index(keyPrefix(key), index);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 缓存key
     * @author: liqingdian
     */
    public static long listSize(String key) {
        try {
            return getList().size(keyPrefix(key));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return 0;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key     键
     * @param value   值
     * @param seconds 时间(秒)
     * @author: liqingdian
     */
    public static boolean listPut(String key, Object value, long seconds) {
        Assert.isNotNull(value, "[value]不能为空");
        try {
            getList().rightPush(keyPrefix(key), value);
            expire(key, seconds);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @author: liqingdian
     */
    public static boolean listPut(String key, Object value) {
        return listPut(key, value, ttl);
    }

    /**
     * 将list放入缓存
     *
     * @param key     键
     * @param value   值
     * @param seconds 时间(秒)
     * @author: liqingdian
     */
    public static boolean listPut(String key, List<Object> value, long seconds) {
        Assert.isNotEmpty(value, "[value]不能为空");
        try {
            getList().rightPushAll(keyPrefix(key), value);
            expire(key, seconds);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @author: liqingdian
     */
    public static boolean listPut(String key, List<Object> value) {
        return listPut(key, value, ttl);
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @author: liqingdian
     */
    public static boolean listUpdate(String key, long index, Object value) {
        Assert.isNotNull(value, "[value]不能为空");
        try {
            getList().set(keyPrefix(key), index, value);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @author: liqingdian
     */
    public static long listRemove(String key, long count, Object value) {
        Assert.isNotNull(value, "[value]不能为空");
        try {
            Long remove = getList().remove(keyPrefix(key), count, value);
            return remove;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return 0;
        }
    }

    /**
     * 获取最大10位唯一编码
     *
     * @param redisKey 缓存Key
     * @param prefix   前缀
     * @param length   限制长度
     * @param init     初始值
     * @author: liqingdian
     */
    public static String getNumCode(String redisKey, String prefix, int length, long init) {
        Assert.isNotBlank(redisKey, "[redisKey]不能为空");
        String siteZero = "0000000000";
        Assert.isTrue(length <= siteZero.length(), "编码限制长度10位");
        prefix = StringUtils.isBlank(prefix) ? "" : prefix;
        long num;
        if (!hasKey(redisKey)) {
            num = increment(redisKey, init + 1);
        } else {
            num = increment(redisKey, 1);
        }
        String numCode = String.valueOf(num);
        int codeLen = prefix.length() + numCode.length();
        Assert.isTrue(codeLen <= length, "生成的编码超长了");
        int site = length - codeLen;
        return Joiner.on("").join(prefix, StringUtils.substring(siteZero, 0, site), numCode);
    }

    /**
     * 获取10位唯一编码
     *
     * @param redisKey 缓存Key
     * @param prefix   前缀
     * @param init     初始值
     * @author: liqingdian
     */
    public static String getNumCode(String redisKey, String prefix, long init) {
        return getNumCode(redisKey, prefix, 10, init);
    }
}
